package hardcode2013;

import com.google.appengine.api.datastore.*;
import com.google.appengine.api.datastore.Query.CompositeFilter;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ItemServ extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	public static final int resultsPerPage = 50;
	
	public static class ItemQueryResult {
		public List<ItemProfile> itemList;
		public int totalItems;
	}
	
	public static ItemQueryResult searchItems(String searchProperty, String searchValue, int page, boolean isAdmin, String username) {
		ItemQueryResult res = new ItemQueryResult();
	    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	    Query query = new Query("Item");
	    if (! isAdmin) {
	    	if (username != null)
		    	query = query.setFilter(new CompositeFilter(CompositeFilterOperator.OR, Arrays.<Filter>asList(
			    		new FilterPredicate("seller", FilterOperator.EQUAL, username),
			    		new FilterPredicate("expiration_datetime", FilterOperator.GREATER_THAN, new Date().getTime())
			    		)));
	    	else
		    	query = query.setFilter(new FilterPredicate("expiration_datetime", FilterOperator.GREATER_THAN, new Date().getTime()));
	    }
	    // query = query.addSort("creation_datetime", Query.SortDirection.DESCENDING);
	    QueryResultList<Entity> queryResList = datastore.prepare(query).asQueryResultList(FetchOptions.Builder.withDefaults());
	    List<ItemProfile> itemList = new ArrayList<ItemProfile>();
	    int totalItems = 0;
	    int offset = (page - 1) * resultsPerPage;
	    for (Entity e : queryResList) {
	    	ItemProfile item = new ItemProfile(
	    			KeyFactory.keyToString(e.getKey()),
	    			(String) e.getProperty("title"),
	    			(String) e.getProperty("description"),
	    			String.format("%.2f", Double.parseDouble(String.valueOf(e.getProperty("price")))),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("creation_datetime"))),
	    			(String) e.getProperty("seller"),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("expiration_datetime"))));
	    	
	    	boolean match = false;
	    	if (searchProperty.equals("title") && item.getTitle().toLowerCase().contains(searchValue.toLowerCase()))
	    		match = true;
	    	else if (searchProperty.equals("description") && item.getDescription().toLowerCase().contains(searchValue.toLowerCase()))
	    		match = true;
	    	else if (searchProperty.equals("price") && item.getPrice().equals(searchValue))
	    		match = true;
	    	
	    	if (match) {
	    		totalItems++;
	    		if (offset == 0)
	    			if (itemList.size() < resultsPerPage) itemList.add(item);
	    		else
	    			offset--;
	    	}
	    }
	    res.totalItems = totalItems;
	    res.itemList = itemList;
	    return res;
	}
	
	public static ItemQueryResult getUserItems(String targetUsername, int page, boolean isAdmin, String username) {
		ItemQueryResult res = new ItemQueryResult();
	    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	    Query query = new Query("Item");
	    if (! (isAdmin || (username != null && targetUsername.equals(username))))
	    	query = query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, Arrays.<Filter>asList(
	    		new FilterPredicate("seller", FilterOperator.EQUAL, targetUsername),
	    		new FilterPredicate("expiration_datetime", FilterOperator.GREATER_THAN, new Date().getTime())
	    		)));
	    else
	    	query = query.setFilter(new FilterPredicate("seller", FilterOperator.EQUAL, targetUsername));	
	    res.totalItems = datastore.prepare(query).countEntities(FetchOptions.Builder.withDefaults());
	    // query = query.addSort("creation_datetime", Query.SortDirection.DESCENDING);
	    FetchOptions options = FetchOptions.Builder.withLimit(resultsPerPage).offset((page - 1) * resultsPerPage);
	    QueryResultList<Entity> queryResList = datastore.prepare(query).asQueryResultList(options);
	    List<ItemProfile> itemList = new ArrayList<ItemProfile>();
	    for (Entity e : queryResList) {
	    	itemList.add(new ItemProfile(
	    			KeyFactory.keyToString(e.getKey()),
	    			(String) e.getProperty("title"),
	    			(String) e.getProperty("description"),
	    			String.format("%.2f", Double.parseDouble(String.valueOf(e.getProperty("price")))),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("creation_datetime"))),
	    			(String) e.getProperty("seller"),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("expiration_datetime")))));
	    }
	    res.itemList = itemList;
	    return res;
	}
	
	public static ItemQueryResult getAllItems(int page, boolean isAdmin, String username) {
		ItemQueryResult res = new ItemQueryResult();
	    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	    Query query = new Query("Item");
	    if (! isAdmin) {
	    	if (username != null)
		    	query = query.setFilter(new CompositeFilter(CompositeFilterOperator.OR, Arrays.<Filter>asList(
			    		new FilterPredicate("seller", FilterOperator.EQUAL, username),
			    		new FilterPredicate("expiration_datetime", FilterOperator.GREATER_THAN, new Date().getTime())
			    		)));
	    	else
		    	query = query.setFilter(new FilterPredicate("expiration_datetime", FilterOperator.GREATER_THAN, new Date().getTime()));
	    }
	    res.totalItems = datastore.prepare(query).countEntities(FetchOptions.Builder.withDefaults());
	    // query = query.addSort("creation_datetime", Query.SortDirection.DESCENDING);
	    FetchOptions options = FetchOptions.Builder.withLimit(resultsPerPage).offset((page - 1) * resultsPerPage);
	    QueryResultList<Entity> queryResList = datastore.prepare(query).asQueryResultList(options);
	    List<ItemProfile> itemList = new ArrayList<ItemProfile>();
	    for (Entity e : queryResList) {
	    	itemList.add(new ItemProfile(
	    			KeyFactory.keyToString(e.getKey()),
	    			(String) e.getProperty("title"),
	    			(String) e.getProperty("description"),
	    			String.format("%.2f", Double.parseDouble(String.valueOf(e.getProperty("price")))),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("creation_datetime"))),
	    			(String) e.getProperty("seller"),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("expiration_datetime")))));
	    }
	    res.itemList = itemList;
	    return res;
	}
	
	public static Entity getItemEntity(Key key) {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try {
			return datastore.get(key);
		} catch (Exception ex) {
			return null;
		}
	}
	public static Entity getItemEntity(String keyStr) {
		return getItemEntity(KeyFactory.stringToKey(keyStr));
	}
	
	public static ItemProfile getItemProfile(Key key) {
		Entity e = getItemEntity(key);
		if (e != null) {
			return new ItemProfile(
	    			KeyFactory.keyToString(e.getKey()),
	    			(String) e.getProperty("title"),
	    			(String) e.getProperty("description"),
	    			String.format("%.2f", Double.parseDouble(String.valueOf(e.getProperty("price")))),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("creation_datetime"))),
	    			(String) e.getProperty("seller"),
	    			new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date((Long) e.getProperty("expiration_datetime"))));
		} else {
			return null;
		}
	}
	public static ItemProfile getItemProfile(String keyStr) {
		return getItemProfile(KeyFactory.stringToKey(keyStr));
	}
	
	public static boolean matchSellerUser(Key key, String username) {
		String seller = (String) getItemEntity(key).getProperty("seller");
		return seller.equals(username);
	}
	public static boolean matchSellerUser(String keyStr, String username) {
		return matchSellerUser(KeyFactory.stringToKey(keyStr), username);
	}
	
	public static String getItemTitle(Key itemKey) {
		return (String) getItemEntity(itemKey).getProperty("title");
	}
	public static String getItemTitle(String itemKeyStr) {
		return getItemTitle(KeyFactory.stringToKey(itemKeyStr));
	}
	
	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String action = req.getParameter("action");
		if (action == null) {HttpUtil.HomeRedirect(resp); return;}
		
		//--- Create Item --- : Member
		if (action.equals("create_item")) { //title, description, price, expirationDate, expirationTime
			
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String title = req.getParameter("title");
			String description = req.getParameter("description");
			String price = req.getParameter("price");
			String expirationDate = req.getParameter("expiration_date");
			String expirationTime = req.getParameter("expiration_time");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (title == null || title.trim().isEmpty())
				{HttpUtil.ExitWithMsg("Title cannot be empty.", req, resp); return;}
			float priceFloat;
			try {
				priceFloat = Float.parseFloat(new DecimalFormat("0.00").format(Float.parseFloat(price)));
			} catch (Exception e) {HttpUtil.ExitWithMsg("Price is invalid.", req, resp); return;}
			if (priceFloat <= 0)
				{HttpUtil.ExitWithMsg("Price must be positive.", req, resp); return;}
			String expirationDateTime = expirationDate + " " + expirationTime;
			long expirationMS;
			try {
				expirationMS = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(expirationDateTime).getTime();
			} catch (ParseException e) {
				{HttpUtil.ExitWithMsg("Expiration date/time is invalid.", req, resp); return;}
			}
			long nowMS = new Date().getTime();
			if (expirationMS < nowMS)
				{HttpUtil.ExitWithMsg("Expiration date cannot be earlier than now.", req, resp); return;}
			
			//perform
			Entity item = new Entity("Item");
			item.setProperty("title", title.trim());
			item.setProperty("description", description);
			item.setProperty("price", new DecimalFormat("0.00").format(priceFloat).toString());
			item.setProperty("creation_datetime", nowMS);
			item.setProperty("seller", username);
			item.setProperty("expiration_datetime", expirationMS);
			DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
			datastore.put(item);
			AdminServ.logEvent(username, "Created Item: " + title.trim());
			SessionUtil.setMessage(req, "Item created successfully.");
			
			//--- Multi Item Action --- : Member
		} else if (action.equals("multi_item_action")) { //itemAction, selItems
			
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String itemAction = req.getParameter("multi_item_action");
			String[] selItems = req.getParameterValues("select_checkbox");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (itemAction == null || itemAction.isEmpty())
				{HttpUtil.HomeRedirect(resp); return;}
			else if (selItems == null || selItems.length == 0)
				{HttpUtil.ExitWithMsg("You must select some items.", req, resp); return;}
			
			//perform
			if (itemAction.equals("delete")) {
				Key[] keyArr = new Key[selItems.length];
				List<String> titleList = new ArrayList<String>();
				for (int i = 0; i < selItems.length; i++)
					keyArr[i] = KeyFactory.stringToKey(selItems[i]);
				DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
				if (! AccountServ.isAdmin(username)) {
					for (Map.Entry<Key,Entity> map : datastore.get(Arrays.asList(keyArr)).entrySet()) {
						String seller = (String) map.getValue().getProperty("seller");
						if (! seller.equals(username)) {HttpUtil.HomeRedirect(resp); return;}
						titleList.add((String) map.getValue().getProperty("title"));
					}
				}
				datastore.delete(keyArr);
				for (String title : titleList)
					AdminServ.logEvent(username, "Deleted Item: " + title);
				SessionUtil.setMessage(req, "Items deleted successfully.");
			}
			
			//--- Single Item Action --- : Member
		} else if (action.equals("single_item_action")) { //itemAction, selKeyStr
			
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String itemAction = req.getParameter("single_item_action");
			String selKeyStr = req.getParameter("single_item_key");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (itemAction == null || itemAction.isEmpty())
				{HttpUtil.HomeRedirect(resp); return;}
			else if (selKeyStr == null || selKeyStr.isEmpty())
				{HttpUtil.HomeRedirect(resp); return;}
			
			//perform
			if (itemAction.equals("delete")) {
				//check
				ItemProfile item = getItemProfile(selKeyStr);
				if (! item.getSeller().equals(username) && ! AccountServ.isAdmin(username)) {HttpUtil.HomeRedirect(resp); return;}
				
				//perform
				Key selKey = KeyFactory.stringToKey(selKeyStr);
				DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
				datastore.delete(selKey);
				AdminServ.logEvent(username, "Deleted Item: " + item.getTitle());
				SessionUtil.setMessage(req, "Item deleted successfully.");
				
			} else if (itemAction.equals("edit")) {
				//load
				String redUrl = req.getParameter("redirect");
				String editUrl = "/edit_item.jsp?item=" + selKeyStr + "&redirect=" + redUrl;
				
				//check
				if (! matchSellerUser(KeyFactory.stringToKey(selKeyStr), username) && ! AccountServ.isAdmin(username)) {HttpUtil.HomeRedirect(resp); return;}
				
				//perform
				resp.sendRedirect(editUrl); return;
				
			} else if (itemAction.equals("mail")) {
				//load
				String redUrl = req.getParameter("redirect");
				String mailUrl = "/send_mail.jsp?item=" + selKeyStr + "&redirect=" + redUrl;
				
				//check
				if (matchSellerUser(KeyFactory.stringToKey(selKeyStr), username)) {HttpUtil.HomeRedirect(resp); return;}
				
				//perform
				resp.sendRedirect(mailUrl); return;
			}
			
			//--- Edit Item --- : Member
		} else if (action.equals("edit_item")) { //itemKeyStr, title, description, price, expirationDate, expirationTime
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String itemKeyStr = req.getParameter("item_key");		
			String title = req.getParameter("title");
			String description = req.getParameter("description");
			String price = req.getParameter("price");
			String expirationDate = req.getParameter("expiration_date");
			String expirationTime = req.getParameter("expiration_time");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (title == null || title.trim().isEmpty())
				{HttpUtil.ExitWithMsg("Title cannot be empty.", req, resp); return;}
			float priceFloat;
			try {
				priceFloat = Float.parseFloat(new DecimalFormat("0.00").format(Float.parseFloat(price)));
			} catch (Exception e) {HttpUtil.ExitWithMsg("Price is invalid.", req, resp); return;}
			if (priceFloat <= 0)
				{HttpUtil.ExitWithMsg("Price must be positive.", req, resp); return;}
			String expirationDateTime = expirationDate + " " + expirationTime;
			long expirationMS;
			try {
				expirationMS = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(expirationDateTime).getTime();
			} catch (ParseException e) {
				{HttpUtil.ExitWithMsg("Expiration date/time is invalid.", req, resp); return;}
			}
			long nowMS = new Date().getTime();
			if (expirationMS < nowMS)
				{HttpUtil.ExitWithMsg("Expiration date cannot be earlier than now.", req, resp); return;}
			if (! matchSellerUser(itemKeyStr, username) && ! AccountServ.isAdmin(username)) {HttpUtil.HomeRedirect(resp); return;}
			
			//perform
			Entity item = getItemEntity(itemKeyStr);
			item.setProperty("title", title.trim());
			item.setProperty("description", description);
			item.setProperty("price", new DecimalFormat("0.00").format(priceFloat).toString());
			item.setProperty("creation_datetime", nowMS);
			item.setProperty("seller", username);
			item.setProperty("expiration_datetime", expirationMS);
			DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
			datastore.put(item);
			AdminServ.logEvent(username, "Edited Item: " + title.trim());
			SessionUtil.setMessage(req, "Item edited successfully.");
		}
		
		HttpUtil.FinalRedirect(req, resp);
	}
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String action = req.getParameter("action");
		if (action == null) {HttpUtil.HomeRedirect(resp); return;}
		
		//--- Search --- : Guest
		if (action.equals("search")) { //search_property, search_value
			//load
			String searchProperty = req.getParameter("search_property");
			String searchValue = req.getParameter("search_value");
			
			//check
			if (searchValue == null || searchValue.trim().isEmpty())
				{HttpUtil.ExitWithMsg("Enter text to search.", req, resp); return;}
			else if (searchProperty == null || searchProperty.isEmpty())
				{HttpUtil.ExitWithMsg("Select a location.", req, resp); return;}
			
			//perform
			String searchUrl = "/search.jsp?search_property=" + searchProperty + "&search_value=" + searchValue.trim();
			resp.sendRedirect(searchUrl); return;
		}
		
		HttpUtil.FinalRedirect(req, resp);
	}
}
